/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using NTasking.ValidationTests.ACATS_3_0.Support;
using NUnit.Framework;
using Rhino.Mocks;

namespace NTasking.ValidationTests.ACATS_3_0.C9
{

    [TestFixture(Description = "")]
    public class C940005 : ValidationTest
    {

        //      Simulate a meter at a freeway on-ramp which, when real-time sensors
        //      determine that the freeway is becoming saturated, triggers stop lights
        //      which control the access of vehicles to prevent further saturation. 
        //      Each on-ramp is represented by a protected object - in this case only
        //      one is shown (Test_Ramp).  The routines to sample and alter the states
        //      of the various sensors, to queue the vehicles on the meter and to
        //      release them are all part of the  protected object and can be shared
        //      by various tasks. Apart from the function/procedure tests this example
        //      has a mix of other tasking features. 


        private const int ClearLevel = 0;
        private const int MinimumLevel = 1;
        private const int ModerateLevel = 2;
        private const int SeriousLevel = 4;
        private const int CriticalLevel = 6;

        // Weighted loads given to each  Sample Point (pure weights, not levels)
        private const int localOverloadWt = 1;
        private const int nextRampInOverloadWt = 1;
        private const int rampJunctionInOverloadWt = 2;

        private const int tcExpectedPassageTotal = 486;

        private const double pulseTimeDelta = Impdef.SwitchToNewTask;

        private static int tcMultiplier = 1; // changed half way through

        private static TestRamp testRamp;
        private static Control control;
        private static IValidationTestResult result;


        private class TestRamp : Protected
        {

            private bool releaseOneVehicle = false;
            private bool meterInUse = false;
            private bool fwyBreakState = false;

            private int rampCount = 0;
            private int rampCountThreshold = 15;

            private int localState = ClearLevel;
            private int nextRampState = ClearLevel;

            private int tcPassageTotal = 0;


            [Entry]
            internal Action WaitAtMeter { get; private set; }


            [ReadWriteOperation]
            internal Action SetLocalOverload { get; private set; }

            [ReadWriteOperation]
            internal Action AddMeterQueue { get; private set; }

            [ReadWriteOperation]
            internal Action SubtractMeterQueue { get; private set; }

            [ReadWriteOperation]
            internal Action TimePulseReceived { get; private set; }

            [ReadWriteOperation]
            internal Action<int> TcPassage { get; private set; }


            [ReadOnlyOperation]
            internal Func<int> NextRampInOverload { get; private set; }

            [ReadOnlyOperation]
            internal Func<int> LocalOverload { get; private set; }

            [ReadOnlyOperation]
            internal Func<int> FreewayOverload { get; private set; }

            [ReadOnlyOperation]
            internal Func<bool> FreewayBreakdown { get; private set; }

            [ReadOnlyOperation]
            internal Func<bool> MeterInUseState { get; private set; }

            [ReadOnlyOperation]
            internal Func<int> TcGetPassageTotal { get; private set; }


            public TestRamp()
            {
                // External call for Meter_in_Use
                ReadOnlyOperation(MeterInUseState, Is: () =>
                {
                    return meterInUse;
                });


                // Trace the paths through the various routines by totaling the
                // weighted call parameters
                ReadWriteOperation(TcPassage, Is: passPoint =>
                {
                    tcPassageTotal += passPoint * tcMultiplier;
                });


                // For the final check of the whole test
                ReadOnlyOperation(TcGetPassageTotal, Is: () =>
                {
                    return tcPassageTotal;
                });


                // These Set/Clear routines are triggered by real-time sensors that
                // reflect traffic state
                ReadWriteOperation(SetLocalOverload, Is: () =>
                {
                    localState = localOverloadWt;
                    if (!meterInUse)
                        StartMeter(); // LOCAL INTERNAL PROCEDURE FROM PROCEDURE
                });


                ReadOnlyOperation(LocalOverload, Is: () =>
                {
                    return localState;
                });


                ReadOnlyOperation(NextRampInOverload, Is: () =>
                {
                    return nextRampState;
                });


                // return the summation of all the load factors
                ReadOnlyOperation(FreewayOverload, Is: () =>
                {
                    // EACH IS A CALL OF A FUNCTION FROM WITHIN A FUNCTION
                    return LocalOverload() + NextRampInOverload();
                });


                // Freeway Breakdown is defined as traffic moving < 5mph
                ReadOnlyOperation(FreewayBreakdown, Is: () =>
                {
                    return fwyBreakState;
                });


                // Keep count of vehicles currently on meter queue - we can't use
                // the 'count because we need the outcall trigger
                ReadWriteOperation(AddMeterQueue, Is: () =>
                {
                    const int tcPassPoint = 22;

                    rampCount += 1;
                    TcPassage(tcPassPoint); // note passage through here
                    if (rampCount > rampCountThreshold)
                    { } // :::: stub, trigger surface street notification
                });


                ReadWriteOperation(SubtractMeterQueue, Is: () =>
                {
                    const int tcPassPoint = 24;

                    rampCount -= 1;
                    TcPassage(tcPassPoint); // note passage through here
                });


                // Here each Vehicle task queues itself awaiting release
                Entry(WaitAtMeter,
                    When: () => releaseOneVehicle,
                Is: () =>
                // EXAMPLE OF ENTRY WITH BARRIERS AND PERSISTENT SIGNAL
                {
                    const int tcPassPoint = 23;

                    TcPassage(tcPassPoint); // note passage through here
                    releaseOneVehicle = false; // Consume the signal
                    // Decrement number of vehicles on ramp 
                    SubtractMeterQueue(); // CALL PROCEDURE FROM WITHIN ENTRY BODY
                });


                ReadWriteOperation(TimePulseReceived, Is: () =>
                {
                    // CALL MULTILEVEL FUNCTION FROM WITHIN PROCEDURE
                    int load = FreewayOverload();

                    // if broken down, no vehicles are released
                    if (!FreewayBreakdown()) // CALL FUNCTION FROM A PROCEDURE
                    {
                        if (load < ModerateLevel)
                            releaseOneVehicle = true;

                        // stub  ::: If other levels, release every other pulse, every third pulse  etc.
                    }
                });
            }


            private void StartMeter()
            {
                meterInUse = true;
                ; // stub  :::: trigger the metering hardware
            }
        }


        private class Control : Protected
        {

            private bool halt = false;


            [ReadWriteOperation]
            internal Action StopNow { get; private set; }


            [ReadOnlyOperation]
            internal Func<bool> Stop { get; private set; }


            public Control()
            {
                ReadWriteOperation(StopNow, Is: () =>
                {
                    halt = true;
                });


                ReadOnlyOperation(Stop, Is: () =>
                {
                    return halt;
                });
            }
        }


        private class PulseTask : Task
        {

            protected override void Body()
            {
                DateTime pulseTime = DateTime.Now;

                while (!control.Stop())
                {
                    Ada.Delay(pulseTime);
                    testRamp.TimePulseReceived(); // causes INTERNAL CALLS
                    pulseTime += TimeSpan.FromSeconds(pulseTimeDelta);
                }
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Unexpected exception in Pulse_Task");
                result.TestFailed();
            }
        }


        // Carrier task. One is created for each vehicle arriving at the ramp
        private class Vehicle : Task
        {
            private const int tcPassPoint = 1;
            private const int tcPassPoint2 = 21;
            private const int tcPassPoint3 = 2;


            protected override void Body()
            {
                testRamp.TcPassage(tcPassPoint); // note passage through here

                if (testRamp.MeterInUseState())
                {
                    testRamp.TcPassage(tcPassPoint2); // note passage
                    // Increment count of number of vehicles on ramp 
                    testRamp.AddMeterQueue(); // CALL a protected PROCEDURE which is also called from within
                    // enter the meter queue
                    testRamp.WaitAtMeter(); // CALL a protected ENTRY
                }

                testRamp.TcPassage(tcPassPoint3); // note passage through here

                // :::: call to the first in the series of the Ramp_Sensors
                //      this "passes" the vehicle from one sensor to the next
            }
            protected override void Exception(Exception ex)
            {
                Console.WriteLine("Unexpected exception in Vehicle Task");
                result.TestFailed();
            }
        }


        // Simulate the arrival of a vehicle at the Ramp_Receiver and the
        // generation of an accompanying carrier task
        private static void NewArrival()
        {
            const int tcPassPoint = 3;

            //new Vehicle().Start();
            new Vehicle().Activate();
            testRamp.TcPassage(tcPassPoint); // Note passage through here
        }


        [Test(Description =
                "Check that the body of a protected function can have internal calls" +
                "to other protected functions and that the body of a protected" +
                "procedure can have internal calls to protected procedures and to " +
                "protected functions.")]
        public void Test()
        {
            result = Mocks.DynamicMock<IValidationTestResult>();

            Ada.Declare(() =>
            {
                control = new Control();
                testRamp = new TestRamp();

                tcMultiplier = 1;

                new PulseTask();

                // Setup text expectations

                Expect.Call(result.TestFailed).Repeat.Never();
                Mocks.ReplayAll();
            },
            Begin: () =>
            {
                // Start test execution

                // Test driver.  This is ALL test control code

                // First simulate calls to the protected functions and procedures
                // from without the protected object
                //
                // CALL FUNCTIONS
                if (testRamp.LocalOverload() != ClearLevel)
                {
                    Console.WriteLine("External Call to Local_Overload incorrect");
                    result.TestFailed();
                }
                if (testRamp.NextRampInOverload() != ClearLevel)
                {
                    Console.WriteLine("External Call to Next_Ramp_in_Overload incorrect");
                    result.TestFailed();
                }
                if (testRamp.FreewayOverload() != ClearLevel)
                {
                    Console.WriteLine("External Call to Freeway_Overload incorrect");
                    result.TestFailed();
                }

                // Now Simulate the arrival of a vehicle to verify path through test
                NewArrival();
                Ada.Delay(pulseTimeDelta * 2); // allow it to pass through the complex

                tcMultiplier = 5; // change the weights for the paths for the next part of the test

                // Simulate a real-time sensor reporting overload
                testRamp.SetLocalOverload(); // CALL A PROCEDURE  (and change levels)

                // CALL FUNCTIONS again
                if (testRamp.LocalOverload() != MinimumLevel)
                {
                    Console.WriteLine("External Call to Local_Overload incorrect - 2");
                    result.TestFailed();
                }
                if (testRamp.FreewayOverload() != MinimumLevel)
                {
                    Console.WriteLine("External Call to Freeway_Overload incorrect - 2");
                    result.TestFailed();
                }

                // Now Simulate the arrival of another vehicle again causing
                // INTERNAL CALLS but following different paths (queuing on the 
                // meter etc.)
                NewArrival();
                Ada.Delay(pulseTimeDelta * 2); // allow it to pass through the complex

                control.StopNow(); // finish test

                if (tcExpectedPassageTotal != testRamp.TcGetPassageTotal())
                {
                    Console.WriteLine("Unexpected paths taken");
                    result.TestFailed();
                }
            });
        }

    }

}
